/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: Step.java,v 1.6 2006/06/06 22:34:34 spericas Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.compiler;

import java.util.Vector;

import com.sun.org.apache.bcel.internal.generic.ALOAD;
import com.sun.org.apache.bcel.internal.generic.ASTORE;
import com.sun.org.apache.bcel.internal.generic.CHECKCAST;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.ICONST;
import com.sun.org.apache.bcel.internal.generic.ILOAD;
import com.sun.org.apache.bcel.internal.generic.ISTORE;
import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.bcel.internal.generic.PUSH;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
import com.sun.org.apache.xml.internal.dtm.Axis;
import com.sun.org.apache.xml.internal.dtm.DTM;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 * @author Morten Jorgensen
 */
final class Step extends RelativeLocationPath {

  /**
   * This step's axis as defined in class Axis.
   */
  private int _axis;

  /**
   * A vector of predicates (filters) defined on this step - may be null
   */
  private Vector _predicates;

  /**
   * Some simple predicates can be handled by this class (and not by the
   * Predicate class) and will be removed from the above vector as they are
   * handled. We use this boolean to remember if we did have any predicates.
   */
  private boolean _hadPredicates = false;

  /**
   * Type of the node test.
   */
  private int _nodeType;

  public Step(int axis, int nodeType, Vector predicates) {
    _axis = axis;
    _nodeType = nodeType;
    _predicates = predicates;
  }

  /**
   * Set the parser for this element and all child predicates
   */
  public void setParser(Parser parser) {
    super.setParser(parser);
    if (_predicates != null) {
      final int n = _predicates.size();
      for (int i = 0; i < n; i++) {
        final Predicate exp = (Predicate) _predicates.elementAt(i);
        exp.setParser(parser);
        exp.setParent(this);
      }
    }
  }

  /**
   * Define the axis (defined in Axis class) for this step
   */
  public int getAxis() {
    return _axis;
  }

  /**
   * Get the axis (defined in Axis class) for this step
   */
  public void setAxis(int axis) {
    _axis = axis;
  }

  /**
   * Returns the node-type for this step
   */
  public int getNodeType() {
    return _nodeType;
  }

  /**
   * Returns the vector containing all predicates for this step.
   */
  public Vector getPredicates() {
    return _predicates;
  }

  /**
   * Returns the vector containing all predicates for this step.
   */
  public void addPredicates(Vector predicates) {
    if (_predicates == null) {
      _predicates = predicates;
    } else {
      _predicates.addAll(predicates);
    }
  }

  /**
   * Returns 'true' if this step has a parent pattern.
   * This method will return 'false' if this step occurs on its own under
   * an element like <xsl:for-each> or <xsl:apply-templates>.
   */
  private boolean hasParentPattern() {
    final SyntaxTreeNode parent = getParent();
    return (parent instanceof ParentPattern ||
        parent instanceof ParentLocationPath ||
        parent instanceof UnionPathExpr ||
        parent instanceof FilterParentPath);
  }

  /**
   * Returns 'true' if this step has a parent location path.
   */
  private boolean hasParentLocationPath() {
    return getParent() instanceof ParentLocationPath;
  }

  /**
   * Returns 'true' if this step has any predicates
   */
  private boolean hasPredicates() {
    return _predicates != null && _predicates.size() > 0;
  }

  /**
   * Returns 'true' if this step is used within a predicate
   */
  private boolean isPredicate() {
    SyntaxTreeNode parent = this;
    while (parent != null) {
      parent = parent.getParent();
      if (parent instanceof Predicate) {
        return true;
      }
    }
    return false;
  }

  /**
   * True if this step is the abbreviated step '.'
   */
  public boolean isAbbreviatedDot() {
    return _nodeType == NodeTest.ANODE && _axis == Axis.SELF;
  }


  /**
   * True if this step is the abbreviated step '..'
   */
  public boolean isAbbreviatedDDot() {
    return _nodeType == NodeTest.ANODE && _axis == Axis.PARENT;
  }

  /**
   * Type check this step. The abbreviated steps '.' and '@attr' are
   * assigned type node if they have no predicates. All other steps
   * have type node-set.
   */
  public Type typeCheck(SymbolTable stable) throws TypeCheckError {

    // Save this value for later - important for testing for special
    // combinations of steps and patterns than can be optimised
    _hadPredicates = hasPredicates();

    // Special case for '.'
    //   in the case where '.' has a context such as book/.
    //   or .[false()] we can not optimize the nodeset to a single node.
    if (isAbbreviatedDot()) {
      _type = (hasParentPattern() || hasPredicates() || hasParentLocationPath()) ?
          Type.NodeSet : Type.Node;
    } else {
      _type = Type.NodeSet;
    }

    // Type check all predicates (expressions applied to the step)
    if (_predicates != null) {
      final int n = _predicates.size();
      for (int i = 0; i < n; i++) {
        final Expression pred = (Expression) _predicates.elementAt(i);
        pred.typeCheck(stable);
      }
    }

    // Return either Type.Node or Type.NodeSet
    return _type;
  }

  /**
   * Translate a step by pushing the appropriate iterator onto the stack.
   * The abbreviated steps '.' and '@attr' do not create new iterators
   * if they are not part of a LocationPath and have no filters.
   * In these cases a node index instead of an iterator is pushed
   * onto the stack.
   */
  public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
    translateStep(classGen, methodGen, hasPredicates() ? _predicates.size() - 1 : -1);
  }

  private void translateStep(ClassGenerator classGen,
      MethodGenerator methodGen,
      int predicateIndex) {
    final ConstantPoolGen cpg = classGen.getConstantPool();
    final InstructionList il = methodGen.getInstructionList();

    if (predicateIndex >= 0) {
      translatePredicates(classGen, methodGen, predicateIndex);
    } else {
      int star = 0;
      String name = null;
      final XSLTC xsltc = getParser().getXSLTC();

      if (_nodeType >= DTM.NTYPES) {
        final Vector ni = xsltc.getNamesIndex();

        name = (String) ni.elementAt(_nodeType - DTM.NTYPES);
        star = name.lastIndexOf('*');
      }

      // If it is an attribute, but not '@*', '@pre:*' or '@node()',
      // and has no parent
      if (_axis == Axis.ATTRIBUTE && _nodeType != NodeTest.ATTRIBUTE
          && _nodeType != NodeTest.ANODE && !hasParentPattern()
          && star == 0) {
        int iter = cpg.addInterfaceMethodref(DOM_INTF,
            "getTypedAxisIterator",
            "(II)" + NODE_ITERATOR_SIG);
        il.append(methodGen.loadDOM());
        il.append(new PUSH(cpg, Axis.ATTRIBUTE));
        il.append(new PUSH(cpg, _nodeType));
        il.append(new INVOKEINTERFACE(iter, 3));
        return;
      }

      SyntaxTreeNode parent = getParent();
      // Special case for '.'
      if (isAbbreviatedDot()) {
        if (_type == Type.Node) {
          // Put context node on stack if using Type.Node
          il.append(methodGen.loadContextNode());
        } else {
          if (parent instanceof ParentLocationPath) {
            // Wrap the context node in a singleton iterator if not.
            int init = cpg.addMethodref(SINGLETON_ITERATOR,
                "<init>",
                "(" + NODE_SIG + ")V");
            il.append(new NEW(cpg.addClass(SINGLETON_ITERATOR)));
            il.append(DUP);
            il.append(methodGen.loadContextNode());
            il.append(new INVOKESPECIAL(init));
          } else {
            // DOM.getAxisIterator(int axis);
            int git = cpg.addInterfaceMethodref(DOM_INTF,
                "getAxisIterator",
                "(I)" + NODE_ITERATOR_SIG);
            il.append(methodGen.loadDOM());
            il.append(new PUSH(cpg, _axis));
            il.append(new INVOKEINTERFACE(git, 2));
          }
        }
        return;
      }

      // Special case for /foo/*/bar
      if ((parent instanceof ParentLocationPath) &&
          (parent.getParent() instanceof ParentLocationPath)) {
        if ((_nodeType == NodeTest.ELEMENT) && (!_hadPredicates)) {
          _nodeType = NodeTest.ANODE;
        }
      }

      // "ELEMENT" or "*" or "@*" or ".." or "@attr" with a parent.
      switch (_nodeType) {
        case NodeTest.ATTRIBUTE:
          _axis = Axis.ATTRIBUTE;
        case NodeTest.ANODE:
          // DOM.getAxisIterator(int axis);
          int git = cpg.addInterfaceMethodref(DOM_INTF,
              "getAxisIterator",
              "(I)" + NODE_ITERATOR_SIG);
          il.append(methodGen.loadDOM());
          il.append(new PUSH(cpg, _axis));
          il.append(new INVOKEINTERFACE(git, 2));
          break;
        default:
          if (star > 1) {
            final String namespace;
            if (_axis == Axis.ATTRIBUTE) {
              namespace = name.substring(0, star - 2);
            } else {
              namespace = name.substring(0, star - 1);
            }

            final int nsType = xsltc.registerNamespace(namespace);
            final int ns = cpg.addInterfaceMethodref(DOM_INTF,
                "getNamespaceAxisIterator",
                "(II)" + NODE_ITERATOR_SIG);
            il.append(methodGen.loadDOM());
            il.append(new PUSH(cpg, _axis));
            il.append(new PUSH(cpg, nsType));
            il.append(new INVOKEINTERFACE(ns, 3));
            break;
          }
        case NodeTest.ELEMENT:
          // DOM.getTypedAxisIterator(int axis, int type);
          final int ty = cpg.addInterfaceMethodref(DOM_INTF,
              "getTypedAxisIterator",
              "(II)" + NODE_ITERATOR_SIG);
          // Get the typed iterator we're after
          il.append(methodGen.loadDOM());
          il.append(new PUSH(cpg, _axis));
          il.append(new PUSH(cpg, _nodeType));
          il.append(new INVOKEINTERFACE(ty, 3));

          break;
      }
    }
  }


  /**
   * Translate a sequence of predicates. Each predicate is translated
   * by constructing an instance of <code>CurrentNodeListIterator</code>
   * which is initialized from another iterator (recursive call),
   * a filter and a closure (call to translate on the predicate) and "this".
   */
  public void translatePredicates(ClassGenerator classGen,
      MethodGenerator methodGen,
      int predicateIndex) {
    final ConstantPoolGen cpg = classGen.getConstantPool();
    final InstructionList il = methodGen.getInstructionList();

    int idx = 0;

    if (predicateIndex < 0) {
      translateStep(classGen, methodGen, predicateIndex);
    } else {
      final Predicate predicate = (Predicate) _predicates.get(predicateIndex--);

      // Special case for predicates that can use the NodeValueIterator
      // instead of an auxiliary class. Certain path/predicates pairs
      // are translated into a base path, on top of which we place a
      // node value iterator that tests for the desired value:
      //   foo[@attr = 'str']  ->  foo/@attr + test(value='str')
      //   foo[bar = 'str']    ->  foo/bar + test(value='str')
      //   foo/bar[. = 'str']  ->  foo/bar + test(value='str')
      if (predicate.isNodeValueTest()) {
        Step step = predicate.getStep();

        il.append(methodGen.loadDOM());
        // If the predicate's Step is simply '.' we translate this Step
        // and place the node test on top of the resulting iterator
        if (step.isAbbreviatedDot()) {
          translateStep(classGen, methodGen, predicateIndex);
          il.append(new ICONST(DOM.RETURN_CURRENT));
        }
        // Otherwise we create a parent location path with this Step and
        // the predicates Step, and place the node test on top of that
        else {
          ParentLocationPath path = new ParentLocationPath(this, step);
          _parent = step._parent = path;      // Force re-parenting

          try {
            path.typeCheck(getParser().getSymbolTable());
          } catch (TypeCheckError e) {
          }
          translateStep(classGen, methodGen, predicateIndex);
          path.translateStep(classGen, methodGen);
          il.append(new ICONST(DOM.RETURN_PARENT));
        }
        predicate.translate(classGen, methodGen);
        idx = cpg.addInterfaceMethodref(DOM_INTF,
            GET_NODE_VALUE_ITERATOR,
            GET_NODE_VALUE_ITERATOR_SIG);
        il.append(new INVOKEINTERFACE(idx, 5));
      }
      // Handle '//*[n]' expression
      else if (predicate.isNthDescendant()) {
        il.append(methodGen.loadDOM());
        // il.append(new ICONST(NodeTest.ELEMENT));
        il.append(new PUSH(cpg, predicate.getPosType()));
        predicate.translate(classGen, methodGen);
        il.append(new ICONST(0));
        idx = cpg.addInterfaceMethodref(DOM_INTF,
            "getNthDescendant",
            "(IIZ)" + NODE_ITERATOR_SIG);
        il.append(new INVOKEINTERFACE(idx, 4));
      }
      // Handle 'elem[n]' expression
      else if (predicate.isNthPositionFilter()) {
        idx = cpg.addMethodref(NTH_ITERATOR_CLASS,
            "<init>",
            "(" + NODE_ITERATOR_SIG + "I)V");

        // Backwards branches are prohibited if an uninitialized object
        // is on the stack by section 4.9.4 of the JVM Specification,
        // 2nd Ed.  We don't know whether this code might contain
        // backwards branches, so we mustn't create the new object until
        // after we've created the suspect arguments to its constructor.
        // Instead we calculate the values of the arguments to the
        // constructor first, store them in temporary variables, create
        // the object and reload the arguments from the temporaries to
        // avoid the problem.
        translatePredicates(classGen, methodGen, predicateIndex); // recursive call
        LocalVariableGen iteratorTemp
            = methodGen.addLocalVariable("step_tmp1",
            Util.getJCRefType(NODE_ITERATOR_SIG),
            null, null);
        iteratorTemp.setStart(
            il.append(new ASTORE(iteratorTemp.getIndex())));

        predicate.translate(classGen, methodGen);
        LocalVariableGen predicateValueTemp
            = methodGen.addLocalVariable("step_tmp2",
            Util.getJCRefType("I"),
            null, null);
        predicateValueTemp.setStart(
            il.append(new ISTORE(predicateValueTemp.getIndex())));

        il.append(new NEW(cpg.addClass(NTH_ITERATOR_CLASS)));
        il.append(DUP);
        iteratorTemp.setEnd(
            il.append(new ALOAD(iteratorTemp.getIndex())));
        predicateValueTemp.setEnd(
            il.append(new ILOAD(predicateValueTemp.getIndex())));
        il.append(new INVOKESPECIAL(idx));
      } else {
        idx = cpg.addMethodref(CURRENT_NODE_LIST_ITERATOR,
            "<init>",
            "("
                + NODE_ITERATOR_SIG
                + CURRENT_NODE_LIST_FILTER_SIG
                + NODE_SIG
                + TRANSLET_SIG
                + ")V");

        // Backwards branches are prohibited if an uninitialized object
        // is on the stack by section 4.9.4 of the JVM Specification,
        // 2nd Ed.  We don't know whether this code might contain
        // backwards branches, so we mustn't create the new object until
        // after we've created the suspect arguments to its constructor.
        // Instead we calculate the values of the arguments to the
        // constructor first, store them in temporary variables, create
        // the object and reload the arguments from the temporaries to
        // avoid the problem.
        translatePredicates(classGen, methodGen, predicateIndex); // recursive call
        LocalVariableGen iteratorTemp
            = methodGen.addLocalVariable("step_tmp1",
            Util.getJCRefType(NODE_ITERATOR_SIG),
            null, null);
        iteratorTemp.setStart(
            il.append(new ASTORE(iteratorTemp.getIndex())));

        predicate.translateFilter(classGen, methodGen);
        LocalVariableGen filterTemp
            = methodGen.addLocalVariable("step_tmp2",
            Util.getJCRefType(CURRENT_NODE_LIST_FILTER_SIG),
            null, null);
        filterTemp.setStart(
            il.append(new ASTORE(filterTemp.getIndex())));
        // create new CurrentNodeListIterator
        il.append(new NEW(cpg.addClass(CURRENT_NODE_LIST_ITERATOR)));
        il.append(DUP);

        iteratorTemp.setEnd(
            il.append(new ALOAD(iteratorTemp.getIndex())));
        filterTemp.setEnd(il.append(new ALOAD(filterTemp.getIndex())));

        il.append(methodGen.loadCurrentNode());
        il.append(classGen.loadTranslet());
        if (classGen.isExternal()) {
          final String className = classGen.getClassName();
          il.append(new CHECKCAST(cpg.addClass(className)));
        }
        il.append(new INVOKESPECIAL(idx));
      }
    }
  }

  /**
   * Returns a string representation of this step.
   */
  public String toString() {
    final StringBuffer buffer = new StringBuffer("step(\"");
    buffer.append(Axis.getNames(_axis)).append("\", ").append(_nodeType);
    if (_predicates != null) {
      final int n = _predicates.size();
      for (int i = 0; i < n; i++) {
        final Predicate pred = (Predicate) _predicates.elementAt(i);
        buffer.append(", ").append(pred.toString());
      }
    }
    return buffer.append(')').toString();
  }
}
